package com.collins.thread.lock;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAProblem {
    private static AtomicInteger plainData = new AtomicInteger(10);
    private static AtomicStampedReference<Integer> stampedData = new AtomicStampedReference<>(10, 1);
    // crate simple dateformat
    private static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    // res for cas result
    private static volatile boolean res;

    public static void main(String[] args) {
        // ABAProblem.plainABA();
        ABAProblem.stampedABA();
    }

    public static void plainABA() {
        // TODO thread-1
        new Thread(() -> {
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " comes in ...");
            // TODO first change 10 -> 11
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: " + plainData.get());
            res = plainData.compareAndSet(10, 11);
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: " + plainData.get()
                    + ", changed result: " + res);
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // TODO second change 11 -> 10
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: " + plainData.get());
            res = plainData.compareAndSet(11, 10);
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: " + plainData.get()
                    + ", changed result: " + res);
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "thread-1").start();

        // TODO thread-2
        new Thread(() -> {
            // let thread-1 finish ABA
            try {
                TimeUnit.MILLISECONDS.sleep(4000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " comes in ...");
            // TODO thread-2 change 10 -> 11
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: " + plainData.get());
            res = plainData.compareAndSet(10, 11);
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: " + plainData.get()
                    + ", changed result: " + res);
        }, "thread-2").start();
    }

    public static void stampedABA() {
        // TODO thread-1
        new Thread(() -> {
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " comes in ...");
            // TODO first get current stamp
            int stamp = stampedData.getStamp();
            // TODO first change 10 -> 11
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: "
                    + stampedData.get(new int[]{stamp}).toString() + " has stamp:"
                    + stampedData.getStamp());
            res = stampedData.compareAndSet(10, 11, stamp, stamp + 1);
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: "
                    + stampedData.get(new int[]{stamp}).toString()
                    + " has stamp:"
                    + stampedData.getStamp()
                    + ", changed result: " + res);
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // TODO second change 11 -> 10
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: "
                    + stampedData.get(new int[]{stamp}).toString()+ " has stamp:"
                    + stampedData.getStamp());
            res = stampedData.compareAndSet(11, 10,
                    stampedData.getStamp(),  stampedData.getStamp() + 1);
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: "
                    + stampedData.get(new int[]{stamp}).toString()
                    + " has stamp:"
                    + stampedData.getStamp()
                    + ", changed result: " + res);
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "thread-1").start();

        // TODO thread-2
        new Thread(() -> {
            // TODO first get initial stamp
            int stamp = stampedData.getStamp();
            // let thread-1 finish ABA
            try {
                TimeUnit.MILLISECONDS.sleep(4000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " comes in ...");
            // TODO thread-2 change 10 -> 11
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: "
                    + stampedData.get(new int[]{stamp}).toString()+ " has stamp:"
                    + stampedData.getStamp());
            res = stampedData.compareAndSet(10, 11, stamp, stamp + 1);
            System.out.println(df.format(new Date(System.currentTimeMillis()))
                    + ": " + Thread.currentThread().getName() + " has value: "
                    + stampedData.get(new int[]{stamp}).toString()
                    + " has stamp:"
                    + stampedData.getStamp()
                    + ", changed result: " + res);
        }, "thread-2").start();
    }
}
